上一篇介紹了 Image Gradient。
我們知道,電腦並不知道圖片裡有沒有狗、有沒有樹,也不知道哪裡是物體。
它真正尋找的是亮度變化最大的地方。
不過,知道這件事之後,又有另一個問題。
如果要判斷某個 Pixel 的亮度變化,電腦到底要怎麼算?
總不能把整張圖片全部拿來比較吧?
其實,只需要觀察附近幾個 Pixel,就能估計亮度的變化。
而最經典的方法之一,就是 Sobel Operator。
Sobel 的想法其實很直接。
如果想知道亮度是不是往左右改變,那就比較左邊和右邊。
如果想知道亮度是不是往上下改變,那就比較上面和下面。
也就是說,Sobel 其實就是在估計亮度在不同方向上的變化。
假設圖片有一條垂直的黑白分界。
0 0 0 255 255
0 0 0 255 255
0 0 0 255 255
左右方向的亮度變化非常大。
上下方向則幾乎沒有變化。
因此:
如果今天換成水平邊界。
0 0 0 0 0
255 255 255 255 255
結果就會反過來。
因此,Sobel 會分別計算:
最後再把兩個方向的資訊結合起來。
上一篇提到,Gradient 也是利用 Convolution 完成的。
因此 Sobel 一樣需要 Kernel。
計算 X 方向時,使用:
-1 0 1
-2 0 2
-1 0 1
計算 Y 方向時,使用:
-1 -2 -1
0 0 0
1 2 1
可以發現,X 方向的 Kernel,左邊都是負數,右邊都是正數。
它其實就是在比較:
左邊和右邊哪一側比較亮。
而 Y 方向也是相同的概念,只是改成比較上方和下方。
如果仔細看:
-1 0 1
-2 0 2
-1 0 1
會發現,中間那一列不是 ±1,而是 ±2。
這代表什麼?
代表 Sobel 認為,距離中心最近的 Pixel,比上下兩側更值得參考。
因此給它更大的權重。
這也是 Sobel 和最簡單的差分運算不同的地方。
它除了估計亮度變化之外,也稍微考慮了周圍 Pixel,因此對雜訊比較不敏感。
OpenCV 已經提供了 Sobel Operator。
import cv2
import matplotlib.pyplot as plt
img = cv2.imread("dog.jpeg", cv2.IMREAD_GRAYSCALE)
grad_x = cv2.Sobel(img, cv2.CV_64F, 1, 0)
grad_y = cv2.Sobel(img, cv2.CV_64F, 0, 1)
其中:
dx=1, dy=0 表示計算 X 方向 Gradient。dx=0, dy=1 表示計算 Y 方向 Gradient。把結果一起顯示。
plt.figure(figsize=(12,4))
plt.subplot(1,3,1)
plt.imshow(img, cmap="gray")
plt.title("Original")
plt.axis("off")
plt.subplot(1,3,2)
plt.imshow(cv2.convertScaleAbs(grad_x), cmap="gray")
plt.title("Gradient X")
plt.axis("off")
plt.subplot(1,3,3)
plt.imshow(cv2.convertScaleAbs(grad_y), cmap="gray")
plt.title("Gradient Y")
plt.axis("off")
plt.tight_layout()
plt.show()
結果:

可以觀察看看,X 和 Y 的結果並不一樣。
有些邊緣只會在其中一張圖變得很明顯。
因為不同方向的亮度變化,本來就不同。
答案是:通常不夠。
因為一張圖片裡物體的邊界不可能全部都是水平或垂直。
除非被壓扁

因此實際應用時,通常會把兩個方向一起考慮。
OpenCV 可以利用 cv2.magnitude() 將兩個方向的 Gradient 合成。
magnitude = cv2.magnitude(grad_x, grad_y)
得到的結果,就是每個 Pixel 的 Gradient 大小。
Gradient 越大,代表亮度變化越劇烈,也越有可能是邊緣。
Sobel 雖然能找到亮度變化。
但它並不知道哪些變化是真正的物體邊界,哪些又只是光線、陰影或雜訊造成的。
因此,Sobel 的結果通常還不是最後的邊緣圖。
它更像是一張: 哪裡可能有邊緣的提示圖。
後續還需要其他方法進一步處理。
今天介紹了 Sobel Operator。
它利用兩組不同方向的 Kernel,估計圖片在 X 和 Y 方向上的亮度變化。
Sobel 真正計算的是 Gradient,而不是直接辨識物體。
不過,因為物體邊界通常伴隨著劇烈的亮度變化,因此 Sobel 常被用來作為邊緣偵測的重要步驟。
明天,我們會介紹另一個和 Sobel 很相似的方法:
Prewitt Operator。
看看它和 Sobel 到底差在哪裡,又為什麼現在大多數情況下更常看到 Sobel。